Preskúmajte bezpečnú komunikáciu medzi rôznymi pôvodmi pomocou PostMessage API. Zistite viac o jeho možnostiach, bezpečnostných rizikách a osvedčených postupoch na zmiernenie zraniteľností vo webových aplikáciách.
Komunikácia medzi rôznymi pôvodmi (Cross-Origin): Bezpečnostné vzory s PostMessage API
V modernom webe aplikácie často potrebujú interagovať so zdrojmi z rôznych pôvodov. Pravidlo rovnakého pôvodu (Same-Origin Policy - SOP) je kľúčovým bezpečnostným mechanizmom, ktorý obmedzuje prístup skriptov k zdrojom z iného pôvodu. Existujú však legitímne scenáre, kde je komunikácia medzi rôznymi pôvodmi nevyhnutná. API postMessage poskytuje riadený mechanizmus na dosiahnutie tohto cieľa, ale je nevyhnutné pochopiť jeho potenciálne bezpečnostné riziká a implementovať vhodné bezpečnostné vzory.
Pochopenie pravidla rovnakého pôvodu (Same-Origin Policy - SOP)
Pravidlo rovnakého pôvodu je základným bezpečnostným konceptom vo webových prehliadačoch. Obmedzuje webové stránky v uskutočňovaní požiadaviek na inú doménu, ako je tá, ktorá stránku poskytla. Pôvod je definovaný schémou (protokolom), hostiteľom (doménou) a portom. Ak sa ktorýkoľvek z týchto prvkov líši, pôvody sa považujú za odlišné. Napríklad:
https://example.comhttps://www.example.comhttp://example.comhttps://example.com:8080
Všetky tieto sú odlišné pôvody a SOP obmedzuje priamy prístup skriptov medzi nimi.
Predstavenie PostMessage API
API postMessage poskytuje bezpečný a riadený mechanizmus pre komunikáciu medzi rôznymi pôvodmi. Umožňuje skriptom posielať správy do iných okien (napr. iframe, nové okná alebo karty) bez ohľadu na ich pôvod. Prijímajúce okno potom môže tieto správy počúvať a spracovať ich.
Základná syntax pre odoslanie správy je:
otherWindow.postMessage(message, targetOrigin);
otherWindow: Odkaz na cieľové okno (napr.window.parent,iframe.contentWindowalebo objekt okna získaný zwindow.open).message: Dáta, ktoré chcete odoslať. Môže to byť akýkoľvek JavaScript objekt, ktorý je možné serializovať (napr. reťazce, čísla, objekty, polia).targetOrigin: Špecifikuje pôvod, na ktorý chcete správu odoslať. Toto je kľúčový bezpečnostný parameter.
Na prijímajúcej strane musíte počúvať udalosť message:
window.addEventListener('message', function(event) {
// ...
});
Objekt event obsahuje nasledujúce vlastnosti:
event.data: Správa odoslaná druhým oknom.event.origin: Pôvod okna, ktoré správu odoslalo.event.source: Odkaz na okno, ktoré správu odoslalo.
Bezpečnostné riziká a zraniteľnosti
Hoci postMessage ponúka spôsob, ako obísť obmedzenia SOP, zároveň prináša potenciálne bezpečnostné riziká, ak nie je implementované opatrne. Tu sú niektoré bežné zraniteľnosti:
1. Nesúlad cieľového pôvodu
Neoverenie vlastnosti event.origin je kritickou zraniteľnosťou. Ak prijímač slepo dôveruje správe, akákoľvek webová stránka môže poslať škodlivé dáta. Vždy overte, či sa event.origin zhoduje s očakávaným pôvodom pred spracovaním správy.
Príklad (Zraniteľný kód):
window.addEventListener('message', function(event) {
// TOTO NEROBTE!
processMessage(event.data);
});
Príklad (Bezpečný kód):
window.addEventListener('message', function(event) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Prijatá správa z nedôveryhodného pôvodu:', event.origin);
return;
}
processMessage(event.data);
});
2. Vkladanie dát (Data Injection)
Spracovanie prijatých dát (event.data) ako spustiteľného kódu alebo ich priame vkladanie do DOM môže viesť k zraniteľnostiam typu Cross-Site Scripting (XSS). Vždy dezinfikujte a overujte prijaté dáta pred ich použitím.
Príklad (Zraniteľný kód):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
document.body.innerHTML = event.data; // TOTO NEROBTE!
}
});
Príklad (Bezpečný kód):
window.addEventListener('message', function(event) {
if (event.origin === 'https://trusted-origin.com') {
const sanitizedData = sanitize(event.data); // Implementujte správnu funkciu na dezinfekciu
document.getElementById('message-container').textContent = sanitizedData;
}
});
function sanitize(data) {
// Tu implementujte robustnú logiku dezinfekcie.
// Napríklad použite DOMPurify alebo podobnú knižnicu
return DOMPurify.sanitize(data);
}
3. Útoky typu Man-in-the-Middle (MITM)
Ak komunikácia prebieha cez nezabezpečený kanál (HTTP), útočník typu MITM môže zachytiť a upraviť správy. Vždy používajte HTTPS pre bezpečnú komunikáciu.
4. Cross-Site Request Forgery (CSRF)
Ak prijímač vykonáva akcie na základe prijatej správy bez riadneho overenia, útočník by mohol potenciálne sfalšovať správy, aby oklamal prijímača a prinútil ho vykonať neúmyselné akcie. Implementujte ochranné mechanizmy proti CSRF, ako je zahrnutie tajného tokenu do správy a jeho overenie na strane prijímača.
5. Používanie zástupných znakov v targetOrigin
Nastavenie targetOrigin na * umožňuje prijať správu akémukoľvek pôvodu. Tomuto by sa malo vyhýbať, pokiaľ to nie je absolútne nevyhnutné, pretože to narúša účel bezpečnosti založenej na pôvode. Ak musíte použiť *, uistite sa, že implementujete ďalšie silné bezpečnostné opatrenia, ako sú napríklad autentifikačné kódy správ (MAC).
Príklad (Vyhnite sa tomuto):
otherWindow.postMessage(message, '*'); // Vyhnite sa používaniu '*', pokiaľ to nie je absolútne nevyhnutné
Bezpečnostné vzory a osvedčené postupy
Na zmiernenie rizík spojených s postMessage dodržiavajte tieto bezpečnostné vzory a osvedčené postupy:
1. Prísne overovanie pôvodu
Vždy overujte vlastnosť event.origin na strane prijímača. Porovnajte ju s preddefinovaným zoznamom dôveryhodných pôvodov. Na porovnanie použite prísnu rovnosť (===).
2. Dezinfekcia a validácia dát
Dezinfikujte a validujte všetky dáta prijaté cez postMessage pred ich použitím. Používajte vhodné techniky dezinfekcie v závislosti od toho, ako budú dáta použité (napr. ošetrenie HTML, kódovanie URL, validácia vstupu). Na dezinfekciu HTML používajte knižnice ako DOMPurify.
3. Autentifikačné kódy správ (MAC)
Zahrňte do správy autentifikačný kód správy (MAC), aby ste zaistili jej integritu a autentickosť. Odosielateľ vypočíta MAC pomocou zdieľaného tajného kľúča a zahrnie ho do správy. Prijímač prepočíta MAC pomocou toho istého zdieľaného tajného kľúča a porovná ho s prijatým MAC. Ak sa zhodujú, správa sa považuje za autentickú a neporušenú.
Príklad (Použitie HMAC-SHA256):
// Odosielateľ
async function sendMessage(message, targetOrigin, sharedSecret) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: message,
signature: signatureHex
};
otherWindow.postMessage(securedMessage, targetOrigin);
}
// Prijímač
async function receiveMessage(event, sharedSecret) {
if (event.origin !== 'https://trusted-origin.com') {
console.warn('Prijatá správa z nedôveryhodného pôvodu:', event.origin);
return;
}
const securedMessage = event.data;
const message = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(message));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Správa je autentická!');
processMessage(message); // Pokračujte v spracovaní správy
} else {
console.error('Overenie podpisu správy zlyhalo!');
}
}
Dôležité: Zdieľaný tajný kľúč musí byť bezpečne vygenerovaný a uložený. Vyhnite sa pevnému zakódovaniu kľúča v kóde.
4. Používanie Nonce a časových pečiatok
Na zabránenie útokom typu replay zahrňte do správy jedinečný nonce (číslo použité raz) a časovú pečiatku. Prijímač potom môže overiť, že nonce nebol predtým použitý a že časová pečiatka je v prijateľnom časovom rámci. Tým sa zmierňuje riziko, že útočník zopakuje predtým zachytené správy.
5. Princíp najmenších oprávnení
Poskytnite druhému oknu iba minimálne potrebné oprávnenia. Napríklad, ak druhé okno potrebuje iba čítať dáta, neumožňujte mu zapisovať dáta. Navrhnite svoj komunikačný protokol s ohľadom na princíp najmenších oprávnení.
6. Politika zabezpečenia obsahu (CSP)
Použite politiku zabezpečenia obsahu (CSP) na obmedzenie zdrojov, z ktorých je možné načítať skripty, a akcií, ktoré môžu skripty vykonávať. To môže pomôcť zmierniť dopad zraniteľností XSS, ktoré by mohli vzniknúť z nesprávneho zaobchádzania s dátami postMessage.
7. Validácia vstupu
Vždy overujte štruktúru a formát prijatých dát. Definujte jasný formát správy a uistite sa, že prijaté dáta tomuto formátu zodpovedajú. Pomáha to predchádzať neočakávanému správaniu a zraniteľnostiam.
8. Bezpečná serializácia dát
Používajte bezpečný formát serializácie dát, ako je JSON, na serializáciu a deserializáciu správ. Vyhnite sa formátom, ktoré umožňujú spúšťanie kódu, ako sú eval() alebo Function().
9. Obmedzenie veľkosti správ
Obmedzte veľkosť správ odosielaných cez postMessage. Veľké správy môžu spotrebovať nadmerné zdroje a potenciálne viesť k útokom typu denial-of-service.
10. Pravidelné bezpečnostné audity
Vykonávajte pravidelné bezpečnostné audity vášho kódu na identifikáciu a riešenie potenciálnych zraniteľností. Venujte osobitnú pozornosť implementácii postMessage a uistite sa, že sú dodržiavané všetky osvedčené bezpečnostné postupy.
Príkladový scenár: Bezpečná komunikácia medzi iframe a jeho rodičom
Zvážte scenár, kde iframe hosťovaný na https://iframe.example.com potrebuje komunikovať so svojou rodičovskou stránkou hosťovanou na https://parent.example.com. Iframe potrebuje poslať užívateľské dáta rodičovskej stránke na spracovanie.
Iframe (https://iframe.example.com):
// Vygenerujte zdieľaný tajný kľúč (nahraďte bezpečnou metódou generovania kľúča)
const sharedSecret = 'VÁŠ_BEZPEČNÝ_ZDIEĽANÝ_TAJNÝ_KĽÚČ';
// Získajte užívateľské dáta
const userData = {
name: 'John Doe',
email: 'john.doe@example.com'
};
// Odošlite užívateľské dáta rodičovskej stránke
async function sendUserData(userData) {
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
const securedMessage = {
data: userData,
signature: signatureHex
};
parent.postMessage(securedMessage, 'https://parent.example.com');
}
sendUserData(userData);
Rodičovská stránka (https://parent.example.com):
// Zdieľaný tajný kľúč (musí sa zhodovať s kľúčom iframe)
const sharedSecret = 'VÁŠ_BEZPEČNÝ_ZDIEĽANÝ_TAJNÝ_KĽÚČ';
window.addEventListener('message', async function(event) {
if (event.origin !== 'https://iframe.example.com') {
console.warn('Prijatá správa z nedôveryhodného pôvodu:', event.origin);
return;
}
const securedMessage = event.data;
const userData = securedMessage.data;
const receivedSignature = securedMessage.signature;
const encoder = new TextEncoder();
const data = encoder.encode(JSON.stringify(userData));
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(sharedSecret),
{ name: "HMAC", hash: "SHA-256" },
false,
["verify"]
);
const signature = await crypto.subtle.sign("HMAC", key, data);
const signatureArray = Array.from(new Uint8Array(signature));
const signatureHex = signatureArray.map(b => b.toString(16).padStart(2, '0')).join('');
if (signatureHex === receivedSignature) {
console.log('Správa je autentická!');
// Spracujte užívateľské dáta
console.log('Užívateľské dáta:', userData);
} else {
console.error('Overenie podpisu správy zlyhalo!');
}
});
Dôležité poznámky:
- Nahraďte
VÁŠ_BEZPEČNÝ_ZDIEĽANÝ_TAJNÝ_KĽÚČbezpečne vygenerovaným zdieľaným tajným kľúčom. - Zdieľaný tajný kľúč musí byť rovnaký v iframe aj na rodičovskej stránke.
- Tento príklad používa HMAC-SHA256 na autentifikáciu správy.
Záver
API postMessage je silný nástroj na umožnenie komunikácie medzi rôznymi pôvodmi vo webových aplikáciách. Je však kľúčové porozumieť potenciálnym bezpečnostným rizikám a implementovať vhodné bezpečnostné vzory na ich zmiernenie. Dodržiavaním bezpečnostných vzorov a osvedčených postupov uvedených v tejto príručke môžete bezpečne používať postMessage na budovanie robustných a bezpečných webových aplikácií.
Nezabudnite vždy uprednostňovať bezpečnosť a byť informovaní o najnovších osvedčených postupoch v oblasti bezpečnosti pre webový vývoj. Pravidelne kontrolujte svoj kód a bezpečnostné konfigurácie, aby ste sa uistili, že vaše aplikácie sú chránené pred potenciálnymi zraniteľnosťami.